import logging

from util.app import ConfigurableEquipment
from util.cache import Cache
from threading import RLock
import datetime
import traceback as trb
import sys

_index = 0
_lock = RLock()
_es = Cache(10)


class Base(ConfigurableEquipment):
    def __init__(self, id=None, name=None):
        super(Base, self).__init__()
        self.id = self.get_id(id)
        self.name = self.id if name is None else name
        self.config = None

    def get_config(self):
        return {
            'id': self.id,
            'name': self.name,
            'class': self.__module__ + '.' + self.__class__.__name__
        }

    def set_config(self, config):
        self.set_single_config(config, 'id', str)
        self.set_single_config(config, 'name')

    def log_name(self):
        return '%s_%s' % (self.__class__.__name__, self.id)

    def log(self, level, msg):
        logging.getLogger(self.log_name()).log(level, '%s: %s' % (self.get_dt(), msg))

    def info(self, msg):
        logging.getLogger(self.log_name()).info('%s: %s' % (self.get_dt(), msg))

    def warning(self, msg):
        logging.getLogger(self.log_name()).warning('%s: %s' % (self.get_dt(), msg))

    def debug(self, msg):
        logging.getLogger(self.log_name()).debug('%s: %s' % (self.get_dt(), msg))

    def error(self, *e):
        e = [trb.format_exc() if isinstance(ee, Exception) else str(ee) for ee in e]
        e = '\n'.join(e)
        global _es
        try:
            _es.get(e)
        except KeyError:
            _es.put(e, e)
            logging.getLogger(self.log_name()).error('%s: Found error in %s %s: %s' % (self.get_dt(), self.__class__.__name__, self.id, e))

    def debug(self, msg):
        logging.getLogger(self.log_name()).debug('%s: %s' % (self.get_dt(), msg))

    @classmethod
    def get_id(cls, name):
        if name is None:
            global _index, _lock
            with _lock:
                _index += 1
                name = 'busObj_%d' % _index
        return name

    def start(self):
        super(Base, self).start()
        self.info('%s %s is started' % (self.__class__.__name__, self.id))

    def stop(self):
        super(Base, self).stop()
        self.info('%s %s is stopped' % (self.__class__.__name__, self.id))

    def pause(self):
        super(Base, self).pause()
        self.info('%s %s is paused' % (self.__class__.__name__, self.id))

    def resume(self):
        super(Base, self).resume()
        self.info('%s %s is resumed' % (self.__class__.__name__, self.id))

    @staticmethod
    def get_dt():
        dt = datetime.datetime.now()
        return dt.strftime('%m-%d %H:%M:%S.%f')

    @staticmethod
    def get_exception_limit():
        global _es
        return _es.size

    @staticmethod
    def set_exception_limit(limit: int):
        assert limit > 0, 'limit must be great than zero'
        global _es
        _es.size = limit


class Synch:
    def __init__(self):
        self.synchors = set()
        self.entered = set()
        self.left = set()

    def add(self, synchor):
        self.synchors.add(synchor)

    def allow_enter(self, synchor):
        assert synchor in self.synchors
        return synchor not in self.entered

    def enter(self, synchor):
        self.entered.add(synchor)

    def num_entered(self):
        return len(self.entered)

    def num_left(self):
        return len(self.left)

    def num_synchors(self):
        return len(self.synchors)

    def leave(self, synchor):
        assert synchor in self.synchors
        if synchor in self.entered:
            self.left.add(synchor)

    def is_last(self):
        return len(self.left) == len(self.synchors)

    def reset(self):
        self.entered.clear()
        self.left.clear()
